package com.lyf.scm.plan.domain.service.impl;

import com.lyf.scm.common.constants.BigDataEsConstants;
import com.lyf.scm.common.constants.CommonConstants;
import com.lyf.scm.common.enums.*;
import com.lyf.scm.plan.api.dto.SalesPlanDirectlyDTO;
import com.lyf.scm.plan.api.dto.UnitRateQueryDTO;
import com.lyf.scm.plan.domain.remote.item.dto.SkuUnitDTO;
import com.lyf.scm.plan.domain.service.CommonService;
import com.lyf.scm.plan.domain.service.RedisCacheService;
import com.lyf.scm.plan.domain.service.SalesPlanDirectlyService;
import com.lyf.scm.plan.insfrastructure.db.dataobject.es.SalesPlanEsEntity;
import com.rome.arch.core.exception.RomeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ScrolledPage;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.lyf.scm.common.constants.CommonConstants.REDIS_CACHE_DIRECT_FORECAST;
import static com.lyf.scm.common.constants.CommonConstants.REDIS_CACHE_SALE_PLAN;
import static com.lyf.scm.common.util.BatchHandlerUtil.splitTo;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;

/**
 * description:
 * 直营预测计划
 *
 * @author: haodong
 * @data: 2020/8/3 10:26
 */
@Slf4j
@Service
public class SalesPlanDirectlyServiceImpl implements SalesPlanDirectlyService {

    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;
    @Resource
    private RedisCacheService redisCacheService;
    @Resource
    private CommonService commonService;


    /**
     * 更新确认量
     *
     * @param paramList
     * @param unitType
     * @param level
     * @return
     */
    @Override
    public Boolean updateConfirm(List<SalesPlanDirectlyDTO> paramList, Long unitType, Integer level) {
        List<IndexQuery> updateList = new ArrayList<>();
        final long SCROLL_TIMEOUT = 5 * 60 * 1000;
        // 转为基础单位
        conversionUnit(paramList, unitType);
        for (SalesPlanDirectlyDTO val : paramList) {
            // 构建查询条件
            NativeSearchQueryBuilder query = getSearchQuery(val);
            ScrolledPage<SalesPlanEsEntity> scroll = (ScrolledPage<SalesPlanEsEntity>) elasticsearchTemplate
                    .startScroll(SCROLL_TIMEOUT,query.build(), SalesPlanEsEntity.class);
            List<SalesPlanEsEntity> esEntities = new ArrayList<>(scroll.getContent());
            while (scroll.hasContent()) {
                scroll = (ScrolledPage<SalesPlanEsEntity>) elasticsearchTemplate.continueScroll(
                        scroll.getScrollId(), SCROLL_TIMEOUT, SalesPlanEsEntity.class);
                esEntities.addAll(scroll.getContent());
            }
            // 总预测量
            BigDecimal countSales = esEntities.stream().map(v -> v.getPredict_sales() == null ?
                    BigDecimal.ZERO : v.getPredict_sales()).reduce(BigDecimal.ZERO, BigDecimal::add);
            // 当前确认量
            BigDecimal confirmQty = val.getConfirmQty();
            for (SalesPlanEsEntity esEntity : esEntities) {
                BigDecimal proportion = esEntity.getPredict_sales().divide(countSales, 8, BigDecimal.ROUND_HALF_UP);
                if(ifUpdate(esEntity,level)){
                    continue;
                }
                BigDecimal newConfirm = confirmQty.multiply(proportion);
                esEntity.setUpdate_time(LocalDateTime.now());
                getUpdateEntity(esEntity, level, newConfirm);
                updateList.add(new IndexQueryBuilder().withObject(esEntity).withId(esEntity.getId()).build());
            }
        }
        if (!CollectionUtils.isEmpty(updateList)) {
            List<List<IndexQuery>> lists = splitTo(updateList, 14000);
            for (List<IndexQuery> list : lists) {
                elasticsearchTemplate.bulkIndex(list);
                elasticsearchTemplate.refresh(SalesPlanEsEntity.class);
            }
        }
        commonService.clearCache(REDIS_CACHE_DIRECT_FORECAST);
        commonService.clearCache(REDIS_CACHE_SALE_PLAN);
        return Boolean.TRUE;
    }

    private boolean ifUpdate(SalesPlanEsEntity esEntity, Integer level) {
        switch (level){
            case 2:
                return esEntity.getConfirm_status_l2().equals(CommonConstants.TWO) ? Boolean.TRUE : Boolean.FALSE;
            case 3:
                return esEntity.getConfirm_status_l3().equals(CommonConstants.TWO) ? Boolean.TRUE : Boolean.FALSE;
            case 4:
                return esEntity.getConfirm_status_l4().equals(CommonConstants.TWO) ? Boolean.TRUE : Boolean.FALSE;
            default:
                return Boolean.FALSE;
        }
    }

    private void conversionUnit(List<SalesPlanDirectlyDTO> paramList, Long unitType) {
        Long baseUnit = SkuUnitTypeEnum.BASIS_UNIT.getUnitType();
        if (baseUnit.equals(unitType)) {
            return;
        }
        List<String> skuList = paramList.stream().map(SalesPlanDirectlyDTO::getSkuCode).distinct().collect(Collectors.toList());
        List<UnitRateQueryDTO> unitList = new ArrayList<>();
        for (String sku : skuList) {
            UnitRateQueryDTO unit = new UnitRateQueryDTO();
            unit.setSkuCode(sku);
            unit.setFromUnitType(unitType);
            unit.setToUnitType(baseUnit);
            unitList.add(unit);
        }
        Map<String, SkuUnitDTO> unitInfo = redisCacheService.getUnitConversionRatio(unitList);
        if (unitInfo == null) {
            return;
        }
        for (SalesPlanDirectlyDTO directly : paramList) {
            SkuUnitDTO skuUnitInfo = unitInfo.get(directly.getSkuCode() + unitType + baseUnit);
            if (skuUnitInfo == null) {
                continue;
            }
            BigDecimal scale = BigDecimal.valueOf(skuUnitInfo.getScale());
            directly.setConfirmQty(directly.getConfirmQty().multiply(scale));
        }
    }

    private NativeSearchQueryBuilder getSearchQuery(SalesPlanDirectlyDTO param) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.filter(termQuery(EsConmulEnum.DATE_KEYWORD.getFName(),param.getPlanStartDate()));
        if (StringUtils.isNotEmpty(param.getProvinceCode())) {
            queryBuilder.filter(termQuery(EsConmulEnum.PROVINCE_CODE_KEYWORD.getFName(), param.getProvinceCode()));
        }
        if (StringUtils.isNotEmpty(param.getProvinceName())) {
            queryBuilder.filter(termQuery(EsConmulEnum.PROVINCE_KEYWORD.getFName(), param.getProvinceName()));
        }
        if (StringUtils.isNotEmpty(param.getCityCode())) {
            queryBuilder.filter(termQuery(EsConmulEnum.CITY_CODE_KEYWORD.getFName(), param.getCityCode()));
        }
        if (StringUtils.isNotEmpty(param.getCityName())) {
            queryBuilder.filter(termQuery(EsConmulEnum.CITY_KEYWORD.getFName(), param.getCityName()));
        }
        if (StringUtils.isNotEmpty(param.getCountyCode())) {
            queryBuilder.filter(termQuery(EsConmulEnum.COUNTY_CODE_KEYWORD.getFName(), param.getCountyCode()));
        }
        if (StringUtils.isNotEmpty(param.getCountyName())) {
            queryBuilder.filter(termQuery(EsConmulEnum.COUNTY_KEYWORD.getFName(), param.getCountyName()));
        }
        if (StringUtils.isNotEmpty(param.getStoreCode())) {
            queryBuilder.filter(termQuery(EsConmulEnum.STORE_KEYWORD.getFName(), param.getStoreCode()));
        }
        queryBuilder.filter(termQuery(EsConmulEnum.SKU_KEYWORD.getFName(), param.getSkuCode()));
        queryBuilder.filter(termQuery(EsConmulEnum.PLAN_TYPE.getFName(), param.getTypeList().get(0)));
        int pageNum = 0;
        int pageSize = 2000;
        return new NativeSearchQueryBuilder().withQuery(queryBuilder)
                .withIndices(BigDataEsConstants.INDEX_NAME_PLAN_SALES_PLAN)
                .withPageable(PageRequest.of(pageNum, pageSize));
    }

    private SalesPlanEsEntity getUpdateEntity(SalesPlanEsEntity esEntity, Integer level, BigDecimal newConfirm) {
        newConfirm = newConfirm.scale() > 3 ? newConfirm.setScale(3,BigDecimal.ROUND_HALF_UP) : newConfirm;
        if (level.equals(CommonConstants.AREA_LEVEL_FOUR)) {
            esEntity.setConfirm_l4(newConfirm);
            esEntity.setConfirm_target_l4(newConfirm);
            return esEntity;
        }
        if (level.equals(CommonConstants.AREA_LEVEL_THREE)) {
            esEntity.setConfirm_l3(newConfirm);
            esEntity.setConfirm_target_l3(newConfirm);
            return esEntity;
        }
        if (level.equals(CommonConstants.AREA_LEVEL_TWO)) {
            esEntity.setConfirm_l2(newConfirm);
            esEntity.setConfirm_target_l2(newConfirm);
            return esEntity;
        }
        return esEntity;
    }

    /**
     * 更新确认状态
     *
     * @param level
     * @param specifiedDate
     * @return
     */
    @Override
    public Boolean updateConfirmType(Integer level, String specifiedDate) {
        String statusKey;
        switch (level) {
            case 2:
                statusKey = "confirm_status_l2";
                break;
            case 3:
                statusKey = "confirm_status_l3";
                break;
            case 4:
                statusKey = "confirm_status_l4";
                break;
            default:
                throw new RomeException(ResponseMsg.PARAM_ERROR.getCode(), ResponseMsg.PARAM_ERROR.getMsg());
        }
        final long SCROLL_TIMEOUT = 5 * 60 * 1000;
        int pageNum = 0;
        int pageSize = 1000;
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(termQuery(EsConmulEnum.PLAN_TYPE.getFName(), SalesPlanTypeEnum.PLAN_SEVENTEEN.getType()));
        queryBuilder.must(termQuery(EsConmulEnum.DATE_KEYWORD.getFName(), specifiedDate));
        queryBuilder.must(termQuery(statusKey, CommonConstants.ONE));
        SearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withIndices(BigDataEsConstants.INDEX_NAME_PLAN_SALES_PLAN)
                .withPageable(PageRequest.of(pageNum, pageSize))
                .build();
        ScrolledPage<SalesPlanEsEntity> scroll = (ScrolledPage<SalesPlanEsEntity>) elasticsearchTemplate
                .startScroll(SCROLL_TIMEOUT, query, SalesPlanEsEntity.class);
        log.warn("总共需处理数据:{}条", scroll.getTotalElements());
        while (scroll.hasContent()) {
            convertAndSaveEs(scroll.getContent(), level);
            scroll = (ScrolledPage<SalesPlanEsEntity>) elasticsearchTemplate.continueScroll(
                    scroll.getScrollId(), SCROLL_TIMEOUT, SalesPlanEsEntity.class);
        }
        commonService.clearCache(REDIS_CACHE_DIRECT_FORECAST);
        commonService.clearCache(REDIS_CACHE_SALE_PLAN);
        return Boolean.TRUE;
    }

    private void convertAndSaveEs(List<SalesPlanEsEntity> esEntities, Integer level) {
        List<IndexQuery> updateList = new ArrayList<>();
        for (SalesPlanEsEntity val : esEntities) {
            switch (level) {
                case 2:
                    val.setConfirm_status_l2(CommonConstants.TWO);
                    break;
                case 3:
                    val.setConfirm_status_l3(CommonConstants.TWO);
                    break;
                case 4:
                    val.setConfirm_status_l4(CommonConstants.TWO);
                    break;
                default:
                    throw new RomeException(ResponseMsg.PARAM_ERROR.getCode(), ResponseMsg.PARAM_ERROR.getMsg());
            }
            val.setUpdate_time(LocalDateTime.now());
            updateList.add(new IndexQueryBuilder().withObject(val).withId(val.getId()).build());
        }
        elasticsearchTemplate.bulkIndex(updateList);
        elasticsearchTemplate.refresh(SalesPlanEsEntity.class);
    }
}


